package top.lyjwn.todo.common.utils;

import java.beans.Transient;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.digest.DigestUtils;

/**
 * 签名工具
 *
 * @author luoyujie
 */
public class SignUtil {

    /**
     * 验证签名
     *
     * @param api_key
     * @param object
     * @param sign
     * @return
     */
    public static boolean verify(String api_key, Object object, String sign) {
        if (sign.equals(getSha256Sign(api_key, object)) || sign.equals(getMd5Sign(api_key, object))) {
            return true;
        }
        return false;
    }

    /**
     * 验证签名
     *
     * @param api_key
     * @param xml
     * @param sign
     * @return
     */
    public static boolean verify(String api_key, String xml, String sign, String signFiledName) {
        Map<String, String> stringStringMap = XMLUtil.convertXmlStrToObject(xml);
        stringStringMap.remove(signFiledName);
        if (sign.equals(getSha256SignByMap(api_key, stringStringMap)) || sign.equals(getMd5SignMap(api_key, stringStringMap))) {
            return true;
        }
        return false;
    }

    /**
     * HMAC-SHA256签名类型
     *
     * @param object
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     * @throws IllegalStateException
     */
    public static String getSha256Sign(String api_key, Object object) {
        String urlkeyValuePair = getUrlkeyValuePair(object);
        String stringSignTemp = urlkeyValuePair + "&key=" + api_key;
        Mac sha256_HMAC = null;
        try {
            sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(api_key.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            // utf-8 : 解决中文加密不一致问题,必须指定编码格式
            return byteArrayToHexString(sha256_HMAC.doFinal(stringSignTemp.getBytes("utf-8"))).toUpperCase();
        } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalStateException
                 | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * HMAC-SHA256签名类型
     *
     * @param map
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     * @throws IllegalStateException
     */
    public static String getSha256SignByMap(String api_key, Map<String, ? extends Object> map) {
        String urlkeyValuePair = getUrlkeyValuePairByMap(map);
        String stringSignTemp = urlkeyValuePair + "&key=" + api_key;
        Mac sha256_HMAC = null;
        try {
            sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(api_key.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            // utf-8 : 解决中文加密不一致问题,必须指定编码格式
            return byteArrayToHexString(sha256_HMAC.doFinal(stringSignTemp.getBytes("utf-8"))).toUpperCase();
        } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalStateException
                 | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * MD5签名类型
     *
     * @param object
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     * @throws IllegalStateException
     */
    public static String getMd5Sign(String api_key, Object object) {
        String urlkeyValuePair = getUrlkeyValuePair(object);
        String stringSignTemp = urlkeyValuePair + "&key=" + api_key;
        return DigestUtils.md5Hex(stringSignTemp).toUpperCase();

    }

    /**
     * MD5签名类型
     *
     * @param map
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     * @throws IllegalStateException
     */
    public static String getMd5SignMap(String api_key, Map<String, ? extends Object> map) {
        String urlkeyValuePair = getUrlkeyValuePairByMap(map);
        String stringSignTemp = urlkeyValuePair + "&key=" + api_key;
        return DigestUtils.md5Hex(stringSignTemp).toUpperCase();

    }

    /**
     * 将加密后的字节数组转换成字符串
     *
     * @param b 字节数组
     * @return 字符串
     */
    private static String byteArrayToHexString(byte[] b) {
        StringBuilder hs = new StringBuilder();
        String stmp;
        for (int n = 0; b != null && n < b.length; n++) {
            stmp = Integer.toHexString(b[n] & 0XFF);
            if (stmp.length() == 1)
                hs.append('0');
            hs.append(stmp);
        }
        return hs.toString().toLowerCase();
    }

    /**
     * url键值对
     *
     * @param object
     * @return
     */
    public static String getUrlkeyValuePair(Object object) {
        String[] filedNames = getFiledNames(object);
        Arrays.sort(filedNames);
        StringBuffer urlKeyValue = new StringBuffer();
        for (String filedName : filedNames) {
            Object value = getFieldValueByName(filedName, object);
            if (value != null) {
                urlKeyValue.append("&" + filedName + "=" + getFieldValueByName(filedName, object).toString());
            }
        }
        String ukv = urlKeyValue.toString();
        return ukv.substring(1);
    }

    /**
     * url键值对
     *
     * @param map
     * @return
     */
    public static String getUrlkeyValuePairByMap(Map<String, ? extends Object> map) {
        Set<String> keySet = map.keySet();
        String[] keys = new String[keySet.size()];
        String[] filedNames = keySet.toArray(keys);
        Arrays.sort(filedNames);
        StringBuffer urlKeyValue = new StringBuffer();
        for (String filedName : filedNames) {
            Object value = map.get(filedName);
            if (value != null) {
                urlKeyValue.append("&" + filedName + "=" + map.get(filedName).toString());
            }
        }
        String ukv = urlKeyValue.toString();
        return ukv.substring(1);
    }

    /**
     * 获取属性名数组
     *
     * @param object
     * @return
     */
    private static String[] getFiledNames(Object object) {
        Field[] fields = object.getClass().getDeclaredFields();
        String[] fieldNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
//            System.out.println(fields[i].getType());
            Field field = fields[i];
            IgnoreSign annotation = field.getAnnotation(IgnoreSign.class);
            if (Optional.ofNullable(annotation).isEmpty()) {
                fieldNames[i] = field.getName();
            }
        }
        return fieldNames;
    }

    /**
     * 获取属性值
     *
     * @param fieldName
     * @param object
     * @return
     */
    private static Object getFieldValueByName(String fieldName, Object object) {
        try {
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = "get" + firstLetter + fieldName.substring(1);
            Method method = object.getClass().getMethod(getter, new Class[]{});
            Object value = method.invoke(object, new Object[]{});
            return value;
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
                 | SecurityException e) {

            return null;
        }
    }


    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface IgnoreSign {

    }

}
